package com.chuangjiangx.member.coupon.ddd.application;

import com.chuangjiangx.commons.exception.BaseException;
import com.chuangjiangx.commons.wx.WXCardSignUtils;
import com.chuangjiangx.commons.wx.jssdk.WXJssdkCardSign;
import com.chuangjiangx.dddbase.domain.shared.Timestamp;
import com.chuangjiangx.member.coupon.ddd.application.command.CreateMbrCouponCommand;
import com.chuangjiangx.member.coupon.ddd.application.command.ModifyMbrCouponCommand;
import com.chuangjiangx.member.coupon.ddd.application.result.VerifyCouponResult;
import com.chuangjiangx.member.coupon.ddd.domain.exception.MbrCouponNotExistException;
import com.chuangjiangx.member.coupon.ddd.domain.model.*;
import com.chuangjiangx.member.coupon.ddd.domain.repository.MbrCouponRepository;
import com.chuangjiangx.member.coupon.ddd.domain.repository.MbrCouponUseStoreRepository;
import com.chuangjiangx.member.coupon.ddd.domain.repository.MbrHasCouponRepository;
import com.chuangjiangx.member.coupon.dao.model.InMbrCouponUseStore;
import com.chuangjiangx.member.coupon.ddd.domain.service.CouponDomainService;
import com.chuangjiangx.member.basic.ddd.domain.service.MemberRedisDomainService;
import com.chuangjiangx.member.basic.ddd.domain.exception.ParamNotNullException;
import com.chuangjiangx.member.basic.ddd.domain.model.MemberId;
import com.chuangjiangx.member.basic.ddd.domain.model.MerchantId;
import com.chuangjiangx.member.basic.ddd.domain.model.StoreId;
import com.chuangjiangx.member.common.utils.MbrKeyManager;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import weixin.popular.api.CardAPI;
import weixin.popular.bean.card.code.decrypt.CodeDecrypt;
import weixin.popular.bean.card.code.decrypt.CodeDecryptResult;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Service
public class MbrCouponApplication {

    private final MbrCouponRepository mbrCouponRepository;
    private final MbrCouponUseStoreRepository mbrCouponUseStoreRepository;
    private final MbrHasCouponRepository mbrHasCouponRepository;
    private final CouponDomainService couponDomainService;
    private final MemberRedisDomainService memberRedisDomainService;
    private final RedisTemplate redisTemplate;

    @Autowired
    public MbrCouponApplication(
            MbrCouponRepository mbrCouponRepository,
            MbrCouponUseStoreRepository mbrCouponUseStoreRepository,
            MbrHasCouponRepository mbrHasCouponRepository,
            CouponDomainService couponDomainService,
            MemberRedisDomainService memberRedisDomainService,
            RedisTemplate redisTemplate) {
        this.mbrCouponRepository = mbrCouponRepository;
        this.mbrCouponUseStoreRepository = mbrCouponUseStoreRepository;
        this.mbrHasCouponRepository = mbrHasCouponRepository;
        this.couponDomainService = couponDomainService;
        this.memberRedisDomainService = memberRedisDomainService;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 新增卡券
     */
    @Transactional(rollbackFor = Exception.class)
    public MbrCoupon createMbrCoupon(CreateMbrCouponCommand command) {
        MbrCouponSourceType sourceType = MbrCouponSourceType.getSourceType(command.getCouponSourceType());
        if (sourceType==MbrCouponSourceType.CLAIMED){
            if (command.getActTimeEnd()==null||command.getActTimeStart()==null||command.getClaimUpperLimit()==null){
                throw new ParamNotNullException();
            }
        }
        //新增卡券

       MbrCoupon mbrCoupon = new MbrCoupon(
                MbrCoupon.genCouponNumber(),
                new MerchantId(command.getMerchantId()), command.getName(), command.getAmount(),
                MbrCouponType.getType(command.getType()), command.getLogo(),
                ValidType.getType(command.getValidType()), command.getValidTimeStart(),
                command.getValidTimeEnd(), command.getClaimedTime(), command.getTotalInventory(),
                command.getTotalInventory(), command.getRemark(), command.getMiniExpendLimit(),
                command.getClaimUpperLimit(), InvalidRemindType.getType(command.getRemindType()),
                command.getUseTimeWeek(), command.getUseTimeDay(),
                sourceType,
                command.getActTimeStart(), command.getActTimeEnd(),command.getWxColor(),command.getSyncType(),
                command.getWxCenterSubTitle(),WxAuditStatus.NOT_AUDITED,null,null);


        mbrCoupon.setTimestamp(new Timestamp(new Date(), new Date()));
        mbrCouponRepository.save(mbrCoupon);
        //关联卡券可使用门店
        relateCouponAndStore(mbrCoupon.getId(),command.getStoreIds());
        return mbrCoupon;
    }

    /**
     * 新增卡券同步至微信
     */
    @Transactional(rollbackFor = Exception.class)
    public void createWxCashCoupon(MbrCouponId mbrCouponId, String merchantName) {
        couponDomainService.createWxCashCard(mbrCouponId,merchantName);
    }

    /**
     * 更新卡券
     */
    @Transactional(rollbackFor = Exception.class)
    public MbrCoupon updateMbrCoupon(ModifyMbrCouponCommand command) {
        MbrCoupon mbrCoupon = mbrCouponRepository.fromCouponNumber(command.getCouponNumber());
        if (mbrCoupon == null) {
            throw new MbrCouponNotExistException();
        }
        MbrCouponSourceType sourceType = MbrCouponSourceType.getSourceType(command.getCouponSourceType());
        if (sourceType==MbrCouponSourceType.CLAIMED){
            if (command.getActTimeEnd()==null||command.getActTimeStart()==null||command.getClaimUpperLimit()==null){
                throw new ParamNotNullException();
            }
        }
        //更新会员卡券，活动开始后不允许修改
        mbrCoupon.updateCoupon(
                command.getName(),command.getAmount(), MbrCouponType.getType(command.getType()),
                sourceType, command.getLogo(),command.getMiniExpendLimit(),command.getClaimUpperLimit(),
                command.getTotalInventory(),ValidType.getType(command.getValidType()),command.getClaimedTime(),
                command.getValidTimeStart(),
                command.getValidTimeEnd(),command.getUseTimeWeek(),command.getUseTimeDay(),
                InvalidRemindType.getType(command.getRemindType()),
                command.getActTimeStart(),command.getRemark(),command.getActTimeEnd(),command.getWxColor(),
                command.getSyncType(),command.getWxCenterSubTitle());
        mbrCouponRepository.update(mbrCoupon);

        //更新关联卡券可使用门店
        mbrCouponUseStoreRepository.deleteByMbrCouponId(mbrCoupon.getId());
        relateCouponAndStore(mbrCoupon.getId(),command.getStoreIds());
        return mbrCoupon;
    }

    /**
     * 更新卡券同步至微信
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateWxCashCoupon(MbrCouponId mbrCouponId,String merchantName) {
        couponDomainService.updateWxCashCard(mbrCouponId,merchantName);
    }

    //关联卡券和使用门店
    private void relateCouponAndStore(MbrCouponId mbrCouponId, String storeIds) {
        if (StringUtils.isNotBlank(storeIds)) {
            String[] ids = storeIds.split(",");
            for (String id : ids) {
                MbrCouponUseStore couponUseStore = new MbrCouponUseStore(mbrCouponId, new StoreId(Long.parseLong(id)));
                mbrCouponUseStoreRepository.save(couponUseStore);
            }
        }
    }

    /**
     * 新增卡券库存
     */
    public void addMbrCouponInventory(String couponNumber, Long amount) {
        MbrCoupon oldMbrCoupon = mbrCouponRepository.fromCouponNumber(couponNumber);
        if (oldMbrCoupon == null) {
            throw new MbrCouponNotExistException();
        }
        //校验卡券库存修改
        oldMbrCoupon.checkInventory(1,amount);
        long newInventory = oldMbrCoupon.getTotalInventory() + amount;
        long newAvailInventory = oldMbrCoupon.getAvailInventory() + amount;
        mbrCouponRepository.inventoryUpdate(oldMbrCoupon,newInventory,newAvailInventory);
    }

    /**
     * 减少卡券库存
     */
    public void subtractMbrCouponInventory(String couponNumber, Long amount) {
        Objects.requireNonNull(couponNumber);
        MbrCoupon oldMbrCoupon = mbrCouponRepository.fromCouponNumber(couponNumber);
        if (oldMbrCoupon == null) {
            throw new MbrCouponNotExistException();
        }
        oldMbrCoupon.checkInventory(2,amount);
        long newInventory = oldMbrCoupon.getTotalInventory() - amount;
        long newAvailInventory = oldMbrCoupon.getAvailInventory() - amount;
        mbrCouponRepository.inventoryUpdate(oldMbrCoupon,newInventory,newAvailInventory);
    }

    /**
     * 提前结束活动
     */
    public void finishActivity(String couponNumber) {
        Objects.requireNonNull(couponNumber);
        MbrCoupon mbrCoupon = mbrCouponRepository.fromCouponNumber(couponNumber);
        if (mbrCoupon == null) {
            throw new MbrCouponNotExistException();
        }
        mbrCoupon.finishActivity();
        mbrCouponRepository.update(mbrCoupon);
    }

    /**
     * 删除卡券-活动未开始
     * @param couponNumber 卡券编号
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteMbrCoupon(String couponNumber) {
        Objects.requireNonNull(couponNumber);
        MbrCoupon mbrCoupon = mbrCouponRepository.fromCouponNumber(couponNumber);
        if (mbrCoupon == null) {
            throw new MbrCouponNotExistException();
        }
        //活动进行中，不准删除
        Date now = new Date();
        if (mbrCoupon.getActTimeStart().after(now) && mbrCoupon.getActTimeEnd().before(now)) {
            throw new IllegalArgumentException("活动已开始,无法删除");
        }
        //删除卡券
        mbrCouponRepository.delete(mbrCoupon.getId());
        //删除卡券使用门店
        mbrCouponUseStoreRepository.deleteByMbrCouponId(mbrCoupon.getId());
    }

    /**
     * 复制卡券
     * @param couponNumber 卡券编号
     */
    public MbrCoupon copyMbrCoupon(String couponNumber) {
        Objects.requireNonNull(couponNumber);
        MbrCoupon mbrCoupon = mbrCouponRepository.fromCouponNumber(couponNumber);
        if (mbrCoupon == null) {
            throw new MbrCouponNotExistException();
        }
        MbrCoupon newCoupon = mbrCoupon.copyMbrCoupon();
        mbrCouponRepository.save(newCoupon);
        return newCoupon;
    }

    /**
     * 生成卡卷领取推广二维码链接
     * @param couponNumber 卡券编号
     */
    public String pushMbrCoupon(String couponNumber) {
        Objects.requireNonNull(couponNumber);
        MbrCoupon mbrCoupon = mbrCouponRepository.fromCouponNumber(couponNumber);
        if (mbrCoupon == null) {
            throw new MbrCouponNotExistException();
        }

        return couponDomainService.pushCoupon(couponNumber,mbrCoupon.getMerchantId().getId());
    }

    /**
     * 会员领取卡券，校验领取上限
     * @param memberId 会员id
     * @param couponNumber 卡券编号
     */
    public MbrHasCoupon memberClaimCoupon(MemberId memberId, String couponNumber) {

        return couponDomainService.memberClaimCoupon(memberId,couponNumber);
    }

    /**
     * 卡券校验
     * 1.校验有效期
     * 2.校验最低消费限制
     * 3.校验使用时段
     * 4.校验使用门店
     */
    public VerifyCouponResult verifyMbrHasCoupon(final String verifyCode, final  Long storeId, final  Long storeUserId, final  BigDecimal orderAmount){
        Objects.requireNonNull(verifyCode);
        MbrHasCoupon mbrHasCoupon = mbrHasCouponRepository.fromVerifyCode(verifyCode);
        if (mbrHasCoupon == null) {
            throw new BaseException("","未找到核销码对应的会员卡券信息");
        }
        MbrCoupon mbrCoupon = mbrCouponRepository.fromId(mbrHasCoupon.getMbrCouponId());
        if (mbrCoupon == null) {
            throw new BaseException("","未找到核销码对应的卡券信息");
        }
        // 冻结卡券
        mbrHasCoupon.lock();
        //校验1，2，3
        mbrCoupon.checkVerifyCode(orderAmount, mbrHasCoupon.getClaimTime());

        //校验4
        if (storeId != null) {
            //门店id不为空校验门店
            List<InMbrCouponUseStore> stores = mbrCouponUseStoreRepository.fromMbrCouponId(mbrHasCoupon.getMbrCouponId());
            //如果卡券对应门店为空，则代表所有门店都可使用
            if (stores != null && stores.size() > 0) {
                boolean match = stores
                        .stream()
                        .anyMatch(i -> Objects.equals(storeId, i.getStoreId()));
                if (! match) {
                    throw new BaseException("","该卡券无法在当前门店使用");
                }
            }
        }
        //卡券验券记录
        mbrHasCoupon.verifyRecord(storeId,storeUserId);
        // 把卡券暂时放入redis,以便于出现卡券核销后不支付情况时做自动解冻
        redisTemplate.opsForValue()
                .set(MbrKeyManager.MBR_COUPON_LOCK_KEY + mbrHasCoupon.getVerifyCode(),
                        mbrHasCoupon.getVerifyCode(),
                        1,TimeUnit.MINUTES);
        this.mbrHasCouponRepository.update(mbrHasCoupon);

        VerifyCouponResult result = new VerifyCouponResult();
        result.setMbrCoupon(mbrCoupon);
        result.setMbrHasCoupon(mbrHasCoupon);
        return result;
    }

    /**
     * 解锁卡券
     * @param verifyCode 卡券核销码
     */
    public void unlockMbrHasCoupon(String verifyCode) {
        Objects.requireNonNull(verifyCode);
        couponDomainService.unlockCoupon(verifyCode);
    }

    /**
     * 微信H5发卡
     * @param merchantId
     * @param cardId
     * @param code
     */
    public WXJssdkCardSign wxGetCard(Long merchantId, String cardId, String code){
        String api_ticket = memberRedisDomainService.getMerchantJsCardTicket(merchantId);
        if(StringUtils.isNotBlank(api_ticket)){
            return WXCardSignUtils.jssdkCardSign(api_ticket,cardId,code,null);
        }else{
            throw new BaseException("","获取签名信息出错");
        }
    }

    /**
     * 微信回调code解码
     * @param merchantId
     * @param encryptCode
     */
    public String codeDecrypt(String encryptCode,MerchantId merchantId,int count){
        if(count>3){
            throw new BaseException("","微信code解码失败");
        }
        //解码code
        CodeDecrypt codeDecrypt = new CodeDecrypt();
        codeDecrypt.setEncryptCode(encryptCode);
        String accessToken = memberRedisDomainService.getMerchantAccessToken(merchantId.getId());
        CodeDecryptResult result =  CardAPI.codeDecrypt(accessToken,codeDecrypt);
        if(result.isSuccess()){
            return result.getCode();
        }else{
            return this.codeDecrypt(encryptCode,merchantId,++count);
        }
    }

    /**
     * 微信回调code解码
     * @param merchantId
     * @param encryptCode
     */
    public String codeDecrypt(String encryptCode,MerchantId merchantId){
        return this.codeDecrypt(encryptCode,merchantId,1);
    }

    /**
     * 微信回调审核成功
     * @param wxCardId
     */
    public void wxAuditSuccess(String wxCardId){
        MbrCoupon mbrCoupon = mbrCouponRepository.fromWxCardId(wxCardId);
        mbrCoupon.wxAuditSuccess();
        mbrCouponRepository.update(mbrCoupon);
    }

    /**
     * 微信回调审核失败
     * @param wxCardId
     */
    public void wxAuditFailed(String wxCardId,String refuseReason){
        MbrCoupon mbrCoupon = mbrCouponRepository.fromWxCardId(wxCardId);
        mbrCoupon.wxAuditFailed(refuseReason);
        mbrCouponRepository.update(mbrCoupon);
    }

    /**
     * 微信领取同步成功
     * @param wxCardId
     */
    public void mbrHasCouponSynced(String wxCardId,String code){
        MbrHasCoupon mbrHasCoupon = mbrHasCouponRepository.fromVerifyCode(code);
        mbrHasCoupon.wxSynced();
        mbrHasCouponRepository.update(mbrHasCoupon);
    }
}
